﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives; 
using System.Windows.Data;
using System.Windows.Input;
using System.Xml;
using System.Xml.Schema;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WITD
{
    /// <summary>
    /// Interaction logic for WorkItemsList.xaml
    /// </summary>
    public partial class WorkItemsList : Window
    {
        Project CurrentProject;
        WorkItemTypeDef WIManager;
        ObservableCollection<CheckedListItem<WItem>> WorkItems;
        ArrayList _workItemIds = new ArrayList();
        BackgroundWorker ProgressWorker;
        bool _didRename = false;
        bool _didDestroy = false;
        
        int Progress = 0;
        String Filter = "";

        System.Threading.AutoResetEvent WorkerSync = new System.Threading.AutoResetEvent(false);
        System.Threading.AutoResetEvent ProgressWait = new System.Threading.AutoResetEvent(false);
               
        /// <summary>
        /// Denotes whether a rename was successful or not.
        /// </summary>
        public bool DidRename
        {
            get
            {
                return _didRename;
            }
        }

        /// <summary>
        /// Denotes whether a destroy was successful or not.
        /// </summary>
        public bool DidDestroy
        {
            get
            {
                return _didDestroy;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">The team project to query.</param>
        /// <param name="parent">The owning Window object instance. It must have been shown at least once to avoid an Exception.</param>
        public WorkItemsList(Project project, Window parent)
        {
            InitializeComponent();
            SetWorkerForRefreshList();
            Owner = parent;
            CurrentProject = project;
            Title += CurrentProject.Name;
            WIManager = new WorkItemTypeDef();
            Filter = "All";
            PopulateWorkItems();
            dgWorkItems.ItemsSource = WorkItems;
            lblWICount.Content = "Work item count: " + dgWorkItems.Items.Count.ToString();
            lblWITDFilter.Text = Filter;
            PopulateWITDs(new ProjectType(project));
            Cursor = Cursors.Arrow;
            Buttons(true);
        }

        /// <summary>
        /// Sets up the background worker for destroying the work items.
        /// <para></para>
        /// This allows it to update progress and change the cursor while updating. Also, this background worker is used 
        /// for progress reporting both during destruction of work items and refreshing the list.
        /// </summary>
        private void SetWorkerForDestroy()
        {
            ProgressWorker = new BackgroundWorker();
            ProgressWorker.DoWork += new DoWorkEventHandler(BGDeleteWorkItems);
            ProgressWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProgressWorker_RunWorkerCompleted);
            ProgressWorker.WorkerReportsProgress = true;
            ProgressWorker.ProgressChanged += new ProgressChangedEventHandler(ProgressWorker_ProgressChanged);
            ProgressWorker.WorkerSupportsCancellation = true;
        }
        
        /// <summary>
        /// Sets up the background worker for refreshing the work items list according to the current filter.
        /// <para></para>
        /// This allows it to update progress and change the cursor while updating. Also, this background worker is used 
        /// for progress reporting both during destruction of work items and refreshing the list.
        /// </summary>
        private void SetWorkerForRefreshList()
        {
            ProgressWorker = new BackgroundWorker();
            ProgressWorker.DoWork += new DoWorkEventHandler(DoRefresh);
            ProgressWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProgressWorker_RunWorkerCompleted);
            ProgressWorker.WorkerReportsProgress = true;
            ProgressWorker.ProgressChanged += new ProgressChangedEventHandler(ProgressWorker_ProgressChanged);
            ProgressWorker.WorkerSupportsCancellation = true;
        }

        /// <summary>
        /// The DoWork handler for the background worker when doing a refresh.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DoRefresh(object sender, DoWorkEventArgs e)
        {
            Progress = 0;
            PopulateWorkItems();
        }

        /// <summary>
        /// The background worker thread for destroying workitems.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void BGDeleteWorkItems(object sender, DoWorkEventArgs e)
        {
            Progress = 0;

            ArrayList selectedWorkItems = e.Argument as ArrayList;
            if (MessageBox.Show("Are you certain you want to permanently destroy the selected work items? " +
                                Environment.NewLine + "You will not be able to recover them or stop the destruction after it starts.",
                                "Destroy Work Items", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
            {
                //occasionally the Array:List contains null entries, so remove them
                for (int i = 0; i < selectedWorkItems.Count; i++)
                {
                    if (selectedWorkItems[i] == null)
                    {
                        selectedWorkItems.RemoveAt(i);
                    }
                }

                String idString = "";
                foreach (int id in selectedWorkItems)
                {
                    idString += id.ToString().Trim() + ",";
                }

                idString = idString.TrimEnd(',');
                ProgressWorker.ReportProgress(++Progress, "Destroying work items " + idString);

                DestroySelected(idString);
                ProgressWait.WaitOne(250);

                Sync();
                PopulateWorkItems();

            }
        }

        /// <summary>
        /// Executes when the background thread ends.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProgressWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            dgWorkItems.ItemsSource = WorkItems;
            lblWICount.Content = "Work item count: " + dgWorkItems.Items.Count.ToString();
            Topmost = true;
            Buttons(true);
            Cursor = Cursors.Arrow;
        }

        /// <summary>
        /// Executes when ReportProgress is called to update UI controls.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProgressWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            String status = e.UserState as String;
            lblStatus.Text = status;
            Buttons(false);
            Cursor = Cursors.Wait;
        }

        /// <summary>
        /// Retrieves all work items for the selected project and assigns them to the ListView's ItemSource property.
        /// </summary>
        private void PopulateWorkItems()
        {
            try
            {
                ProgressWorker.ReportProgress(++Progress, "Getting work items...");

                WorkItems = new ObservableCollection<CheckedListItem<WItem>>();
                foreach (WorkItem wi in WorkItemTypeDef.GetAllWorkItems(CurrentProject, CurrentProject.Store, Filter))
                {
                    CheckedListItem<WItem> newItem = new CheckedListItem<WItem>(new WItem(wi), false);
                    //this is the data source for the ListView
                    WorkItems.Add(newItem);
                }
                
                ProgressWorker.ReportProgress(++Progress, "");
             }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Attempts to destroy each work item selected by the user. Passes the result of method GetSelectedIds to DoWork() method.
        /// </summary>
        private void DestroySelectedWorkItems()
        {
            Cursor = Cursors.Wait;
            Topmost = false;
            ProgressWorker.RunWorkerAsync(GetSelectedIds());
        }

        /// <summary>
        /// Builds a list of the IDs of the work items selected by the user.
        /// </summary>
        /// <returns>An ArrayList of IDs of selected work items.</returns>
        private ArrayList GetSelectedIds()
        {
            ArrayList selectedWorkItems = new ArrayList();
            foreach (CheckedListItem<WItem> item in this.dgWorkItems.SelectedItems)
            {
                selectedWorkItems.Add(item.Item.Id);
            }

            return selectedWorkItems;
        }

        /// <summary>
        /// Attempts to destroy all work items in the specified list of IDs.
        /// </summary>
        /// <param name="ids">List of IDs of work items to destroy.</param>
        private void DestroySelected(String ids)
        {
            try
            {
                TfsUtility utility = new TfsUtility();

                String commandLine = String.Format(" destroywi /collection:{0} /id:{1} /noprompt", CurrentProject.Store.TeamProjectCollection.Uri.ToString(), ids);
                utility.RunProcess(utility.WitCommandName, commandLine);

            }
            catch (Exception ex)
            {
                ProgressWorker.ReportProgress(0, "Error: " + ex.Message);
                MessageBox.Show("Error: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Removes links from a list of work items.
        /// </summary>
        /// <param name="ids">A list of work item ids.</param>
        private void RemoveLinks(ArrayList ids)
        {
            foreach (int id in ids)
            {
                WorkItem wi = CurrentProject.Store.GetWorkItem(id);
                foreach (WorkItemLink link in wi.WorkItemLinks)
                {
                    wi.WorkItemLinks.Remove(link);
                }
            }
        }

        /// <summary>
        /// Updates the status label with new content.
        /// </summary>
        /// <param name="status">The new status to display.</param>
        private void UpDateStatus(String status)
        {
            lblStatus.Text = status;
        }

        /// <summary>
        /// Clears the status from the display.
        /// </summary>
        private void ClearStatus()
        {
            lblStatus.Text = "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="project"></param>
        private void PopulateWITDs(ProjectType project)
        {
            cboWITD.Items.Clear();

            foreach (WorkItemType wittype in project.WitTypeCollection)
            {
                CheckedListItem<WorkItemType> newItem = new CheckedListItem<WorkItemType>(wittype, true);
                cboWITD.Items.Add(newItem);
            }

            cboWITD.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Item.Name", System.ComponentModel.ListSortDirection.Ascending));
         }


        /// <summary>
        /// Destroys the selected work items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDestroy_Click(object sender, RoutedEventArgs e)
        {
            if (this.dgWorkItems.SelectedItems.Count > 0)
            {
                Buttons(false);
                Cursor = Cursors.Wait;
                SetWorkerForDestroy();
                DestroySelectedWorkItems();
                Cursor = Cursors.Arrow;
                Buttons(true);
            }
        }

        /// <summary>
        /// Close this form.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnClose_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Updates the display with current number of selected work items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WorkItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            lblSelectedCout.Content = this.dgWorkItems.SelectedItems.Count.ToString() + " selected work items";
        }

        

        /// <summary>
        /// Enables or disables the form's buttons.
        /// </summary>
        /// <param name="enable">True to enable all buttons. Otherwise false.</param>
        private void Buttons(bool enable)
        {
            btnClose.IsEnabled = enable;
            btnDestroy.IsEnabled = enable;
            btnRebuildcache.IsEnabled = enable;
            btnRefresh.IsEnabled = enable;
            btnRefreshFilter.IsEnabled = enable;
        }

        /// <summary>
        /// Attempts to refresh the cache for the current WorkItemStore and after 5 seconds, syncs the client to the cached data.
        /// <para></para>
        /// If the SyncReconnect fails, then a reconnect to the current project occurs to get the updated project information.
        /// </summary>
        private void SyncReconnect()
        {
            System.Threading.AutoResetEvent ev = new System.Threading.AutoResetEvent(false);

            CurrentProject.Store.RefreshCache();
            ev.WaitOne(5000);
            if (!CurrentProject.Store.SyncToCache()) { Reconnect(CurrentProject); } else { }
        }

        /// <summary>
        /// Attempts to refresh the cache for the current WorkItemStore and after 5 seconds, syncs the client to the cached data.
        /// <para></para>
        /// If the SyncReconnect fails, then a reconnect to the current project occurs to get the updated project information.
        /// </summary>
        private void Sync()
        {
            System.Threading.AutoResetEvent ev = new System.Threading.AutoResetEvent(false);

            CurrentProject.Store.RefreshCache();
            ev.WaitOne(5000);
            CurrentProject.Store.SyncToCache();
        }

        /// <summary>
        /// Reconnects to the project collection and work item store. This enables us to get the updated view of the store for the ui.
        /// </summary>
        /// <param name="project">The project that we want to refresh the view of.</param>
        private void Reconnect(Project project)
        {
            TfsTeamProjectCollection collection = new TfsTeamProjectCollection(project.Store.TeamProjectCollection.Uri);
            collection.EnsureAuthenticated();

            WorkItemStore store = (WorkItemStore)collection.GetService(typeof(WorkItemStore));
            CurrentProject = store.Projects[project.Name];
        }

        /// <summary>
        /// Calls the SyncReconnect method to recache and sync with the cache. Often does not actually recache, but nothing can be done about it.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
            SyncReconnect();
        }
        /// <summary>
        /// Calls the rebuildcache command of WitAdmin via the WorkItemTypedef object.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRebuildcache_Click(object sender, RoutedEventArgs e)
        {
            WIManager.Refresh(CurrentProject.Store.TeamProjectCollection.Uri.ToString());
        }
                
        /// <summary>
        /// Occurs when a WIT is checked in the Filter ComboBox.
        /// <para></para>
        /// Updates the filter used to query the project's work items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Chk_Checked(object sender, RoutedEventArgs e)
        {
            bool allChecked = true;

            Filter = "";
            if (!cboWITD.HasItems)
            {
                return;
            }
            else
            {
                foreach (CheckedListItem<WorkItemType> item in cboWITD.Items)
                {
                    if (item.IsChecked)
                    {
                        Filter += item.Item.Name + ",";
                    }
                    else
                        allChecked = false;
                }
                if (allChecked) { Filter = "All"; }
                lblWITDFilter.Text = Filter.Trim().TrimEnd(',');
            }
        }

        /// <summary>
        /// Occurs when a WIT is clicked in the Filter ComboBox.
        /// <para></para>
        /// Updates the filter used to query the project's work items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void chk_Click(object sender, RoutedEventArgs e)
        {
            bool allChecked = true;

            Filter = "";
            if (!cboWITD.HasItems)
            {
                return;
            }
            else
            {
                foreach (CheckedListItem<WorkItemType> item in cboWITD.Items)
                {
                    if (item.IsChecked)
                    {
                        Filter += item.Item.Name + ",";
                    }
                    else
                        allChecked = false;
                }
                if (allChecked) { Filter = "All"; }
                lblWITDFilter.Text = Filter.Trim().TrimEnd(',');
            }
        }

        /// <summary>
        /// Occurs when a WIT is unchecked in the Filter ComboBox.
        /// <para></para>
        /// Updates the filter used to query the project's work items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Chk_Unchecked(object sender, RoutedEventArgs e)
        {
            bool allChecked = true;

            Filter = "";
            if (!cboWITD.HasItems)
            {
                return;
            }
            else
            {
                foreach (CheckedListItem<WorkItemType> item in cboWITD.Items)
                {
                    if (item.IsChecked)
                    {
                        Filter += item.Item.Name + ",";
                    }
                    else
                        allChecked = false;
                }
                if (allChecked) { Filter = "All"; }
                lblWITDFilter.Text = Filter.Trim().TrimEnd(',');
            }
        }

        /// <summary>
        /// Refreshes the list of work items using the filter to narrow the work item types returned.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRefreshFilter_Click(object sender, RoutedEventArgs e)
        {
            SetWorkerForRefreshList();
            Cursor = Cursors.Wait;
            ProgressWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Opens the ComboBox when the mouse hovers over any part of the ComboBox.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cboWITD_MouseEnter(object sender, MouseEventArgs e)
        {
            cboWITD.IsDropDownOpen = true;
        }
                
        /// <summary>
        /// Closes the open ComboBox when the mouse leaves the control's boundaries.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cboWITD_MouseMove(object sender, MouseEventArgs e)
        {
             //Get a ref to the ComboBox  
            ComboBox cbo = (ComboBox)sender;

            //Get a ref to the ComboBox'es popup (which is what displays the available items)  
            Popup p = (Popup)cbo.Template.FindName("PART_Popup", cbo);

            //The DropDown/popup is to close when   
            // - it is still open  
            // - the mouse is no longer over the popup  
            // - the IsMouseDirectlyOver returns true (which, albeit strange, is true when the mouse  
            //   neither over the popup NOR the cbo itself  
            if (cbo.IsDropDownOpen && !p.IsMouseOver && cbo.IsMouseDirectlyOver)
                cbo.IsDropDownOpen = false;

        } 
    }

    /// <summary>
    /// A data class to store displayable information about work items.
    /// </summary>
    public class WItem
    {
        private String _id;
        private int id;
        private String _title;
        private String _state;
        private String _workItemType;
        private String _lastUpdate;
        private String _assignedTo;

        /// <summary>
        /// Constructor: Populates the id, Title, State, Last Update, WorkItemType, and assigned to fields for display.
        /// </summary>
        /// <param name="wi">The work item to display.</param>
        public WItem(WorkItem wi)
        {
            _id = wi.Id.ToString();
            id = wi.Id;
            _title = wi.Title;
            _state = wi.State;
            _lastUpdate = wi.ChangedDate.ToShortDateString() + " " + wi.ChangedDate.ToShortTimeString();
            _workItemType = wi.Type.Name;
            _assignedTo = wi.Fields["Assigned To"].Value.ToString();
        }

        /// <summary>
        /// The value of the AssignedTo field.
        /// </summary>
        public String Assigned
        {
            get
            {
                return _assignedTo;
            }
        }
        /// <summary>
        /// WorkItem Id.
        /// </summary>
        public int Id
        {
            get
            {
                return id;
            }
        }

        /// <summary>
        /// The WorkItemType of the WorkItem.
        /// </summary>
        public String WorkItemType
        {
            get
            {
                return _workItemType;
            }
        }

        /// <summary>
        /// The WorkItem id as a string.
        /// </summary>
        public String ID
        {
            get
            {
                return _id;
            }
        }

        /// <summary>
        /// The WorkItem Title field.
        /// </summary>
        public String Title
        {
            get
            {
                return _title;
            }
        }

        /// <summary>
        /// The WorkItem State value.
        /// </summary>
        public String State
        {
            get
            {
                return _state;
            }
        }

        /// <summary>
        /// The WorkItem LastUpdate field.
        /// </summary>
        public String LastUpdate
        {
            get
            {
                return _lastUpdate;
            }
        }
    }
}


